#include "../../config.h"

#include <glibmm/i18n.h>

#include "../application.h"
#include "p_menu.h"

#include "../gtkversion.h"

namespace gpyp {
namespace pref {

//========================
// Menu::Selector
Menu::Selector::Selector(Application &app) {
  RefConfMenu menu = app.conf().menu();
  set_active(add(menu->yp(), _("YellowPage")));
  add(menu->ch(),            _("Channel"));
  add(menu->tool(),          _("Tool"));
  add(menu->text(),          _("Text"));
  add(menu->proc(),          _("Process"));
  add(menu->pc_relay(),      _("PeerCast relay"));
  add(menu->pc_connection(), _("PeerCast connection"));
}

//========================
// Menu::Manager
Gtk::TreeIter Menu::Manager::get_iter(ConfMenu::RefMenu item) {
  for(Gtk::TreeIter iter = children_.begin();
      iter != children_.end(); ++iter) {
    if (iter->get_value(editor_.column_item_) == item) {
      return iter;
    }
  }
  return Gtk::TreeIter();
}
ConfMenu::RefMenu Menu::Manager::get_item(const Gtk::TreeIter &iter) {
  if (iter) return iter->get_value(editor_.column_item_);
  return ConfMenu::RefMenu();
}

void Menu::Manager::on_item_added(ConfMenu::RefMenu item) {
  Gtk::TreeIter iter = editor_.treestore_->append(children_);
  Gtk::TreeRow  row  = *iter;
  RefManager manager(new Manager(editor_, this, item, iter->children()));
  row[editor_.column_item_]    = item;
  row[editor_.column_manager_] = manager;
  editor_.get_selection()->select(iter);
}
void Menu::Manager::on_item_removed(ConfMenu::RefMenu item) {
  Gtk::TreeIter iter = get_iter(item);
  if (iter) {
    Glib::RefPtr<Gtk::TreeSelection> selection = editor_.get_selection();
    Gtk::TreeIter selected = selection->get_selected();
    if (selected && selected == iter) {
      Gtk::TreeIter iter_sel = iter;
      if (iter_sel != --children_.end()) {
        ++iter_sel;
      } else {
        if (iter_sel == children_.begin()) {
          iter_sel = children_;
        } else {
          --iter_sel;
        }
      }
      if (iter_sel) selection->select(iter_sel);
    }
    editor_.treestore_->erase(iter);
  }
}
void Menu::Manager::on_item_reordered(ConfMenu::RefMenu item,
                                      ConfMenu::RefMenu prev,
                                      ConfMenu::RefMenu next) {
  Gtk::TreeIter iter      = get_iter(item);
  Gtk::TreeIter iter_prev = get_iter(prev);
  Gtk::TreeIter iter_next = get_iter(next);
  Glib::RefPtr<Gtk::TreeStore> treestore = editor_.treestore_;
  if (iter) {
    if (iter_next) {
      treestore->move(iter, iter_next);
    } else {
      treestore->move(iter, ++iter_prev);
    }
  }
}
void Menu::Manager::on_cleared() {
  if (parent_) {
    for(Gtk::TreeIter iter = children_.begin();
        iter != children_.end(); ++iter) {
      editor_.treestore_->erase(iter);
    }
  } else {
    editor_.treestore_->clear();
  }
}

void Menu::Manager::up(ConfMenu::RefMenu item) {
  Gtk::TreeIter iter = get_iter(item);
  if (iter && iter != children_.begin()) {
    menu_->reorder_item(item, ConfMenu::RefMenu(), get_item(--iter));
  }
}
void Menu::Manager::down(ConfMenu::RefMenu item) {
  Gtk::TreeIter iter = get_iter(item), next = iter;
  if (iter && ++next != children_.end()) {
    menu_->reorder_item(item, get_item(next), ConfMenu::RefMenu());
  }
}

Menu::Manager *Menu::Manager::get_parent() {
  return parent_;
}
ConfMenu::RefMenu Menu::Manager::get_menu() {
  return menu_;
}

Menu::Manager::Manager(Editor               &editor,
                       Manager              *parent,
                       ConfMenu::RefMenu     menu,
                       Gtk::TreeNodeChildren children) :
  editor_   (editor),
  parent_   (parent),
  menu_     (menu),
  children_ (children)
{
  for(ConfMenu::Menu::iterator iter = menu_->begin();
      iter != menu_->end(); ++iter) {
    on_item_added(*iter);
  }
#define CONN_MENU(S)                                    \
  conn_##S##_ = menu_->signal_##S()                     \
    .connect(sigc::mem_fun(*this, &Manager::on_##S));
  CONN_MENU(item_added);
  CONN_MENU(item_removed);
  CONN_MENU(item_reordered);
  CONN_MENU(cleared);
#undef CONN_MENU
}

//========================
// Menu::Editor
ConfMenu::RefMenu Menu::Editor::get_item(const Gtk::TreeIter &iter) {
  if (iter) {
    return iter->get_value(column_item_);
  } else {
    return ConfMenu::RefMenu();
  }
}
ConfMenu::RefMenu Menu::Editor::get_item(const Gtk::TreePath &path) {
  return get_item(treestore_->get_iter(path));
}
ConfMenu::RefMenu Menu::Editor::get_item(const Glib::ustring &path) {
  return get_item(Gtk::TreePath(path));
}

void Menu::Editor::icon_on_cell_data(Gtk::CellRenderer   *cell,
                                     const Gtk::TreeIter &iter) {
  ConfMenu::RefMenu menu = get_item(iter);
  if (menu) {
    dynamic_cast<CellRendererIcon *>(cell)->set_icon_name(*menu->icon());
  }
}
void Menu::Editor::label_on_cell_data(Gtk::CellRenderer   *cell,
                                      const Gtk::TreeIter &iter) {
  ConfMenu::RefMenu menu = get_item(iter);
  if (menu) {
    Gtk::CellRendererText *renderer =
      dynamic_cast<Gtk::CellRendererText *>(cell);
    if (menu->get_type() == ConfMenu::ITEM_TYPE_SEPARATOR) {
      renderer->property_text()           = _("=== Separator ===");
      renderer->property_foreground_gdk() = Gdk::Color("gray");
      renderer->property_foreground_set() = true;
      renderer->property_editable()       = false;
    } else {
      renderer->property_text()           = *menu->label();
      renderer->property_foreground_set() = false;
      renderer->property_editable()       = true;
    }
  }
}
void Menu::Editor::command_on_cell_data(Gtk::CellRenderer   *cell,
                                        const Gtk::TreeIter &iter) {
  ConfMenu::RefMenu menu = get_item(iter);
  if (menu) {
    Gtk::CellRendererText *renderer =
      dynamic_cast<Gtk::CellRendererText *>(cell);

    renderer->property_editable()       = false;
    renderer->property_foreground_set() = false;
    switch(menu->get_type()) {
    case ConfMenu::ITEM_TYPE_SEPARATOR:
      renderer->property_text() = Glib::ustring();
      break;
    case ConfMenu::ITEM_TYPE_SPECIAL:
      switch(*menu->sptype()) {
      case ConfMenu::SPECIAL_TYPE_PLAYWITH:
        renderer->property_text() = "*** Play with menu ***";
        renderer->property_foreground_gdk() = Gdk::Color("gray");
        renderer->property_foreground_set() = true;
        break;
      default:
        renderer->property_text() = "*** Bug: invalid value ***";
        renderer->property_foreground_gdk() = Gdk::Color("red");
        renderer->property_foreground_set() = true;
        break;
      }
      break;
    default:
      renderer->property_text()     = *menu->command();
      renderer->property_editable() = true;
    }
  }
}

void Menu::Editor::icon_on_edited(const Glib::ustring &path,
                                  const Glib::ustring &new_icon_name) {
  get_item(path)->icon() = new_icon_name;
}
void Menu::Editor::label_on_edited(const Glib::ustring &path,
                                   const Glib::ustring &new_text) {
  get_item(path)->label() = new_text;
}
void Menu::Editor::command_on_edited(const Glib::ustring &path,
                                     const Glib::ustring &new_text) {
  get_item(path)->command() = new_text;
}

void Menu::Editor::add_item(sigc::slot<ConfMenu::RefMenu, Manager &> func) {
  Gtk::TreeIter iter = get_selection()->get_selected();
  RefManager manager = iter ? iter->get_value(column_manager_) : RefManager();
  Manager   *parent  = manager ? manager->get_parent() : manager_.get();
  ConfMenu::RefMenu menu = func(*parent);
  if (iter) {
    if (iter == parent->children_.begin()) {
      parent->get_menu()
        ->reorder_item(menu, ConfMenu::RefMenu(), manager->get_menu());
    } else {
      RefManager manager = (--iter)->get_value(column_manager_);
      parent->get_menu()
        ->reorder_item(menu, manager->get_menu(), ConfMenu::RefMenu());
    }
  }
}

void Menu::Editor::up() {
  Gtk::TreeIter iter = get_selection()->get_selected();
  if (iter) {
    Manager *manager = iter->get_value(column_manager_)->get_parent();
    (manager ? manager : manager_.get())->up(iter->get_value(column_item_));
  }
}
void Menu::Editor::down() {
  Gtk::TreeIter iter = get_selection()->get_selected();
  if (iter) {
    Manager *manager = iter->get_value(column_manager_)->get_parent();
    (manager ? manager : manager_.get())->down(iter->get_value(column_item_));
  }
}
void Menu::Editor::add() {
  add_item(&ConfMenu::Menu::add_item);
}
void Menu::Editor::add_separator() {
  add_item(&ConfMenu::Menu::add_separator);
}
void Menu::Editor::add_special(const Glib::ustring  &label, ConfMenu::SpecialType sptype) {
  add_item(sigc::bind(sigc::mem_fun(*this, &Editor::add_sp_cb), label, sptype));
}
void Menu::Editor::add_child() {
  Gtk::TreeIter iter = get_selection()->get_selected();
  if (iter) {
    iter->get_value(column_manager_)->get_menu()->add_item();
  }
}
void Menu::Editor::remove() {
  Gtk::TreeIter iter = get_selection()->get_selected();
  if (iter) {
    RefManager manager = iter->get_value(column_manager_);
    ConfMenu::RefMenu parent =
      manager->get_parent() ? manager->get_parent()->get_menu() : menu_;
    parent->remove_item(manager->get_menu());
  }
}

void Menu::Editor::set(ConfMenu::RefMenu menu) {
  treestore_->clear();
  menu_ = menu;
  manager_.reset(new Manager(*this, NULL, menu_, treestore_->children()));
}

Menu::Editor::Editor(Application &/*app*/, Gtk::Window &parent) :
  renderer_icon_ (parent),
  column_icon_   (_("Icon"),    renderer_icon_),
  column_label_  (_("Label"),   renderer_label_),
  column_command_(_("Command"), renderer_command_)
{
  columns_.add(column_item_);
  columns_.add(column_manager_);
  treestore_ = Gtk::TreeStore::create(columns_);
  set_model(treestore_);

#define CELL_DATA(N)                                                    \
  column_##N##_.set_cell_data_func(renderer_##N##_, sigc::mem_fun(*this, &Editor::N##_on_cell_data))
  CELL_DATA(icon);
  CELL_DATA(label);
  CELL_DATA(command);
#undef CELL_DATA

#define CONN_EDITED(N, S)                                       \
  renderer_##N##_.signal_##S()                                  \
    .connect(sigc::mem_fun(*this, &Editor::N##_on_##S))
  CONN_EDITED(icon,    edited);
  CONN_EDITED(label,   edited);
  CONN_EDITED(command, edited);
#undef CONN_EDITED

  append_column(column_icon_);
  append_column(column_label_);
  append_column(column_command_);

  set_expander_column(column_label_);
#ifdef GTK_TREEVIEW_HAVE_TREE_LINES
  set_enable_tree_lines(true);
#endif
}

//========================
// Menu
void Menu::sp_playwith_on_activate() {
  editor_.add_special(_("Play with ..."), ConfMenu::SPECIAL_TYPE_PLAYWITH);
}
void Menu::add_special_on_clicked() {
  menu_sp_.popup(1, GDK_CURRENT_TIME);
}

Menu::Menu(Application &app, Gtk::Window &parent) :
  Gtk::VBox      (false, 2),
  selector_      (app),
  editor_        (app, parent),
  mi_sp_playwith_(_("Play with ..."))
{
  pack_start(selector_, false, false);

  Gtk::ScrolledWindow *sw1 = Gtk::manage(new Gtk::ScrolledWindow());
  sw1->add(editor_);
  sw1->set_shadow_type(Gtk::SHADOW_IN);
  sw1->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);

  Gtk::VButtonBox *v1 =
    Gtk::manage(new Gtk::VButtonBox(Gtk::BUTTONBOX_START, 2));
  Gtk::Button *b_u = Gtk::manage(new Gtk::Button(Gtk::Stock::GO_UP));
  Gtk::Button *b_d = Gtk::manage(new Gtk::Button(Gtk::Stock::GO_DOWN));
  Gtk::Button *b_a = Gtk::manage(new Gtk::Button(Gtk::Stock::ADD));
  Gtk::Button *b_s = Gtk::manage(new Gtk::Button(_("_Separator"), true));
  Gtk::Button *b_p = Gtk::manage(new Gtk::Button(_("S_pecial"),   true));
  Gtk::Button *b_c = Gtk::manage(new Gtk::Button(_("_Child"),     true));
  Gtk::Button *b_r = Gtk::manage(new Gtk::Button(Gtk::Stock::REMOVE));
  b_u->signal_clicked().connect(sigc::mem_fun(editor_, &Editor::up));
  b_d->signal_clicked().connect(sigc::mem_fun(editor_, &Editor::down));
  b_a->signal_clicked().connect(sigc::mem_fun(editor_, &Editor::add));
  b_s->signal_clicked().connect(sigc::mem_fun(editor_, &Editor::add_separator));
  b_p->signal_clicked().connect(sigc::mem_fun(*this,   &Menu::add_special_on_clicked));
  b_c->signal_clicked().connect(sigc::mem_fun(editor_, &Editor::add_child));
  b_r->signal_clicked().connect(sigc::mem_fun(editor_, &Editor::remove));
  v1->pack_start(*b_u, false, false);
  v1->pack_start(*b_d, false, false);
  v1->pack_start(*b_a, false, false);
  v1->pack_start(*b_s, false, false);
  v1->pack_start(*b_p, false, false);
  v1->pack_start(*b_c, false, false);
  v1->pack_start(*b_r, false, false);

  Gtk::HBox *h1 = Gtk::manage(new Gtk::HBox(false, 2));
  h1->pack_start(*sw1);
  h1->pack_start(*v1, false, false);
  pack_start(*h1);

  editor_.set(selector_.get_active());
  selector_.signal_selected()
    .connect(sigc::mem_fun(editor_, &Editor::set));

  mi_sp_playwith_.signal_activate()
    .connect(sigc::mem_fun(*this, &Menu::sp_playwith_on_activate));
  menu_sp_.append(mi_sp_playwith_);
  menu_sp_.show_all();
}

}/*pref*/
}/*gpyp*/
